﻿using System;
using System.Collections.Generic;
using System.IO;

using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Graphics;

namespace Fusion
{
    /// <summary>
    /// Manages generic Types, provides functionality for loading, file updates, and directory watching.
    /// </summary>
    /// <typeparam name="T">The Type to manage</typeparam>
    public class ResourceManager<T>
    {
        // Properties
		public Dictionary<string, T> data = new Dictionary<string, T>();
#if WINDOWS
		FileSystemWatcher watcher = new FileSystemWatcher();
#endif
		string extension = "";
        // Events
        public event StringDelegate Created;
        public event StringDelegate Changed;

        // Content folders - locations of specific Types
        public string typeFolder
        {
            get
            {
                if( contentFolders.ContainsKey( typeof( T ) ) )
                    return contentFolders[typeof( T )];
                else
                    return "/";
            }
        }
        static Dictionary<Type,string> contentFolders = new Dictionary<Type, string>()
        {
            { typeof(Texture2D), "Textures/" },
            { typeof(SpriteFont), "Fonts/" },
            { typeof(SoundEffect), "Sounds/" },
#if SILVERLIGHT
#else
            { typeof(Model), "Models/" },
            { typeof(Effect), "Effects/" },
#endif
        };

        // Constructors
        public ResourceManager()
        { }
        public ResourceManager( string folder, string ext )
        {
#if WINDOWS
            extension = ext;
            Watch( folder, ext );
#endif
        }
                
        #region Management

        /// <summary>
        /// Loads content from the content library
        /// </summary>
        /// <param name="name">The XNA asset name to load</param>
        public void Load( string name )
        {
            // Don't try and load if the type doesn't have a content folder specified
            if( contentFolders.ContainsKey( typeof( T ) ) == false )
                return;


            // Load from Content Library
            try
                { data[name] = Engine.content.Load<T>(contentFolders[typeof(T)] + name); }
            catch (Microsoft.Xna.Framework.Content.ContentLoadException)
            {
                // Load from File instead
                LoadFile( typeFolder + name + "." + extension );
            }


            // Raise Event
            if( Created != null )
                Created( name );
        }

        /// <summary> Loads content from disk </summary>
        /// <param name="fullPath">The file path/name.ext</param>
        public void LoadFile( string fullPath )
        {
            // with file extension
			string filename = fullPath.Substring( fullPath.LastIndexOf( '/' ) + 1 );
			filename = filename.Substring( filename.LastIndexOf( '\\' ) + 1 );
            // without file extension
            string name = filename.Substring( 0, filename.LastIndexOf( '.' ) );

            // Decide which type of event to raise
            bool changed = data.ContainsKey( name );
			
#if WINDOWS
            // Load appropraite content type
			if( typeof( T ) == typeof( Texture2D ) )
			{
				try { data[name] = (T)(object)Texture2D.FromFile( Engine.graphicsDevice, fullPath ); }
				catch( FileNotFoundException )
				{ return; }
				catch( DirectoryNotFoundException )
				{ return; }
			}
#endif
            // else if( typeof(T) == typeof( some other thing ) )
            //  data[name] = (T)(object)Loadit( fullPath );

            
            // Raise appropriate event
            if( changed == true )
            {
                if( Changed != null )
                    Changed( name );
            }
            else
            {
                if( Created != null )
                    Created( name );
            }
        }

        /// <summary> Inserts an object of T into the manager </summary>
        /// <param name="name">The name to refer to the object, usually the filename sans extension</param>
        /// <param name="toInsert">The object to insert. Must be same Type as manager</param>
        public void Add( string name, T toInsert )
        { data[name] = toInsert; }

        /// <summary>
        /// Retrieves an object from the manager
        /// </summary>
        /// <param name="name">Reference name - usually the filename without extension</param>
        /// <returns>Specified Type of the manager</returns>
        public T Get( string name )
        {
			if( data.ContainsKey( name ) == false )
			{
				if( typeof( T ) == typeof( Texture2D ) || typeof( T ) == typeof( SpriteFont ) || typeof(T) == typeof(SoundEffect) )
					Load( name );
			}
			if( data.ContainsKey( name ) == false )
				return default(T);
			else
				return data[name];
        }
        #endregion
        
        #region The Watching
		
#if WINDOWS
        /// <summary> Begin watching the default folder for this manager's Type </summary>
        public void Watch( string ext )
        { Watch(typeFolder, ext); }

        /// <summary> Begin watching a specified folder for files to be loaded into the manager </summary>
        /// <param name="folder">The folder to watch</param>
        public void Watch( string folder, string ext )
        {
            // Watching of unFolders is disallowed
            if( Directory.Exists( folder ) == true )
            {
                watcher.Path = folder;

                // Only watch for content-appropriate filetypes
                if( typeof( T ) == typeof( Texture2D ) )
                    watcher.Filter = "*." + ext;

                // Set events
                watcher.Created += new FileSystemEventHandler( Reload );
                watcher.Changed += new FileSystemEventHandler( Reload );
                watcher.Renamed += new RenamedEventHandler( Reload );
                watcher.EnableRaisingEvents = true;
            }
        }

        /// <summary>
        /// Scans the contents of the folder currently being watched, and loads all appropriate files into the manager
        /// </summary>
        public void Scan()
        {
			Scan( watcher.Path );
		}
        /// <summary>
        /// Scan the contents of a folder, and loads all appropriate files into the manager
        /// </summary>
        /// <param name="folder">Path of the folder to scan</param>
        public void Scan( string folder )
        {
            // There's no hope for ghost folders
            if( Directory.Exists( folder ) == true )
            {
                // Check every file in the folder
                string[] files = Directory.GetFiles( folder );
                foreach( string filePath in files )
                    LoadFile( filePath );
            }
        }
		
        // Event raised whenever a file is changed or created on disk.
        private void Reload( object source, FileSystemEventArgs e )
        {
			LoadFile( e.FullPath );
		}
#endif
        #endregion

    }
}
